/**
 * Copyright (C) 2013 Open Whisper Systems
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package com.appspot.eusms.transport;

import android.app.PendingIntent;
import android.content.Context;
import android.telephony.SmsManager;
import android.util.Log;

import com.appspot.eusms.database.model.SmsMessageRecord;
import com.appspot.eusms.recipients.Recipient;
import com.appspot.eusms.sms.MultipartSmsMessageHandler;
import com.appspot.eusms.sms.OutgoingPrekeyBundleMessage;
import com.appspot.eusms.sms.OutgoingTextMessage;
import com.appspot.eusms.sms.SmsTransportDetails;
import com.appspot.eusms.util.TextSecurePreferences;

import org.whispersystems.textsecure.crypto.MasterSecret;
import org.whispersystems.textsecure.crypto.SessionCipher;
import org.whispersystems.textsecure.crypto.protocol.CiphertextMessage;
import org.whispersystems.textsecure.storage.RecipientDevice;
import org.whispersystems.textsecure.storage.Session;

import java.util.ArrayList;

public class SmsTransport extends BaseTransport {

    private final Context context;
    private final MasterSecret masterSecret;

    public SmsTransport(Context context, MasterSecret masterSecret) {
        this.context = context.getApplicationContext();
        this.masterSecret = masterSecret;
    }

    public void deliver(SmsMessageRecord message) throws UndeliverableMessageException,
            InsecureFallbackApprovalException {
        if (!TextSecurePreferences.isSmsNonDataOutEnabled(context) && !TextSecurePreferences.isSmsFallbackEnabled(context)) {
            throw new UndeliverableMessageException("SMS Transport is not enabled!");
        }

        if (message.isSecure() || message.isKeyExchange() || message.isEndSession()) {
            deliverSecureMessage(message);
        } else {
            deliverPlaintextMessage(message);
        }
    }

    private void deliverSecureMessage(SmsMessageRecord message) throws UndeliverableMessageException,
            InsecureFallbackApprovalException {
        MultipartSmsMessageHandler multipartMessageHandler = new MultipartSmsMessageHandler();
        OutgoingTextMessage transportMessage = OutgoingTextMessage.from(message);

        if (message.isSecure() || message.isEndSession()) {
            transportMessage = getAsymmetricEncrypt(masterSecret, transportMessage);
        }

        ArrayList<String> messages = multipartMessageHandler.divideMessage(transportMessage);
        ArrayList<PendingIntent> sentIntents = constructSentIntents(message.getId(), message.getType(), messages, message.isSecure());
        ArrayList<PendingIntent> deliveredIntents = constructDeliveredIntents(message.getId(), message.getType(), messages);

        Log.w("SmsTransport", "Secure divide into message parts: " + messages.size());

        for (int i = 0; i < messages.size(); i++) {
            // XXX moxie@thoughtcrime.org 1/7/11 -- There's apparently a bug where for some unknown recipients
            // and messages, this will throw an NPE.  I have no idea why, so I'm just catching it and marking
            // the message as a failure.  That way at least it doesn't repeatedly crash every time you start
            // the app.
            // d3sre 12/10/13 -- extended the log file to further analyse the problem
            try {
                SmsManager.getDefault().sendTextMessage(message.getIndividualRecipient().getNumber(), null, messages.get(i),
                        sentIntents.get(i),
                        deliveredIntents == null ? null : deliveredIntents.get(i));
            } catch (NullPointerException npe) {
                Log.w("SmsTransport", npe);
                Log.w("SmsTransport", "Recipient: " + message.getIndividualRecipient().getNumber());
                Log.w("SmsTransport", "Message Total Parts/Current: " + messages.size() + "/" + i);
                Log.w("SmsTransport", "Message Part Length: " + messages.get(i).getBytes().length);
                throw new UndeliverableMessageException(npe);
            } catch (IllegalArgumentException iae) {
                Log.w("SmsTransport", iae);
                throw new UndeliverableMessageException(iae);
            }
        }

    }

    private void deliverPlaintextMessage(SmsMessageRecord message)
            throws UndeliverableMessageException {
        ArrayList<String> messages = SmsManager.getDefault().divideMessage(message.getBody().getBody());
        ArrayList<PendingIntent> sentIntents = constructSentIntents(message.getId(), message.getType(), messages, false);
        ArrayList<PendingIntent> deliveredIntents = constructDeliveredIntents(message.getId(), message.getType(), messages);
        String recipient = message.getIndividualRecipient().getNumber();

        // XXX moxie@thoughtcrime.org 1/7/11 -- There's apparently a bug where for some unknown recipients
        // and messages, this will throw an NPE.  I have no idea why, so I'm just catching it and marking
        // the message as a failure.  That way at least it doesn't repeatedly crash every time you start
        // the app.
        // d3sre 12/10/13 -- extended the log file to further analyse the problem
        try {
            SmsManager.getDefault().sendMultipartTextMessage(recipient, null, messages, sentIntents, deliveredIntents);
        } catch (NullPointerException npe) {
            Log.w("SmsTransport", npe);
            Log.w("SmsTransport", "Recipient: " + recipient);
            Log.w("SmsTransport", "Message Parts: " + messages.size());

            try {
                for (int i = 0; i < messages.size(); i++) {
                    SmsManager.getDefault().sendTextMessage(recipient, null, messages.get(i),
                            sentIntents.get(i),
                            deliveredIntents == null ? null : deliveredIntents.get(i));
                }
            } catch (NullPointerException npe2) {
                Log.w("SmsTransport", npe);
                throw new UndeliverableMessageException(npe2);
            }
        }
    }

    private ArrayList<PendingIntent> constructSentIntents(long messageId, long type,
                                                          ArrayList<String> messages, boolean secure) {
        ArrayList<PendingIntent> sentIntents = new ArrayList<PendingIntent>(messages.size());

        for (String ignored : messages) {
            sentIntents.add(PendingIntent.getBroadcast(context, 0,
                    constructSentIntent(context, messageId, type, secure, false),
                    0));
        }

        return sentIntents;
    }

    private ArrayList<PendingIntent> constructDeliveredIntents(long messageId, long type, ArrayList<String> messages) {
        if (!TextSecurePreferences.isSmsDeliveryReportsEnabled(context)) {
            return null;
        }

        ArrayList<PendingIntent> deliveredIntents = new ArrayList<PendingIntent>(messages.size());

        for (String ignored : messages) {
            deliveredIntents.add(PendingIntent.getBroadcast(context, 0,
                    constructDeliveredIntent(context, messageId, type),
                    0));
        }

        return deliveredIntents;
    }

    private OutgoingTextMessage getAsymmetricEncrypt(MasterSecret masterSecret,
                                                     OutgoingTextMessage message)
            throws InsecureFallbackApprovalException {
        Recipient recipient = message.getRecipients().getPrimaryRecipient();
        RecipientDevice recipientDevice = new RecipientDevice(recipient.getRecipientId(),
                RecipientDevice.DEFAULT_DEVICE_ID);

        if (!Session.hasEncryptCapableSession(context, masterSecret, recipient, recipientDevice)) {
            throw new InsecureFallbackApprovalException("No session exists for this secure message.");
        }

        String body = message.getMessageBody();
        SmsTransportDetails transportDetails = new SmsTransportDetails();
        SessionCipher sessionCipher = SessionCipher.createFor(context, masterSecret, recipientDevice);
        byte[] paddedPlaintext = transportDetails.getPaddedMessageBody(body.getBytes());
        CiphertextMessage ciphertextMessage = sessionCipher.encrypt(paddedPlaintext);
        String encodedCiphertext = new String(transportDetails.getEncodedMessage(ciphertextMessage.serialize()));

        if (ciphertextMessage.getType() == CiphertextMessage.PREKEY_TYPE) {
            message = new OutgoingPrekeyBundleMessage(message, encodedCiphertext);
        } else {
            message = message.withBody(encodedCiphertext);
        }

        return message;
    }
}
